package com.yjzx.util.service.aop.ref.method;

import com.yjzx.util.common.util.map.MyRefTimeMap;
import com.yjzx.util.common.util.unmodifiable.FixedUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author yjzx
 * @date 2024/7/5
 * @description 缓存切面
 */
@Aspect
@Component
public class RefAspect {
    private final Map<Method, MyRefTimeMap> refMap = new ConcurrentHashMap<>();

    @Pointcut("execution(public * com..service..*.*(..))")
    public void execute() {
    }

    @Around("execute()&&@annotation(annotation)")
    public Object doAround(ProceedingJoinPoint joinPoint, RefAnnotation annotation) throws Throwable {
        if (annotation != null) {
            if (annotation.enable()) {
                return around(joinPoint, annotation);
            }

        }
        return joinPoint.proceed();

    }

    /**
     * 返回值切面，根据返回值和注解删除指定方法的缓存
     * @param joinPoint
     * @param annotation
     * @param method
     */
    @AfterReturning(value = "execute()&&@annotation(annotation)", returning = "method")
    public void doAfter(JoinPoint joinPoint, RefClearAnnotation annotation, Method method) {
        if (annotation != null) {
            refMap.remove(method);
        }
    }

    /**
     * 缓存切面主体逻辑
     * @param joinPoint
     * @param annotation
     * @return
     * @throws Throwable
     */
    private Object around(ProceedingJoinPoint joinPoint, RefAnnotation annotation) throws Throwable {
        try {
            Method method = getMethod(joinPoint);

            MyRefTimeMap map = refMap.get(method);
            if (map == null) {
                map = new MyRefTimeMap<>(method.getReturnType(), false, annotation.expires());
                refMap.put(method, map);
            }
            ArrayList<Object> param = getParam(joinPoint);
            Object o = map.get(param);
            if (o == null) {
                o = joinPoint.proceed();
                o = unmodifiable(annotation, method, o);
                map.put(param, o, annotation.refClazz());
            }
            return o;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return joinPoint.proceed();
        }
    }

    /**
     * 缓存内容开启不可修改
     * @param annotation 缓存注解
     * @param method 开启缓存的方法
     * @param o 方法返回值
     * @return
     */
    private Object unmodifiable(RefAnnotation annotation, Method method, Object o) {
        Class<?> type = method.getReturnType();
        if (type == void.class || type == Void.class) {
            return o;
        }
        RefModification modification = annotation.enableImmutable();
        if (modification != null && modification.enable()) {
            if (modification.enableMap()) {
                if (o instanceof Map && type.equals(Map.class)) {
                    o = FixedUtil.toFixedMap((Map<?, ?>) o);
                }
            }
            if (modification.enableCollection()) {
                if (o instanceof List && type.equals(List.class)) {
                    o = FixedUtil.toFixedList((List<?>) o);
                } else if (o instanceof Set && type.equals(Set.class)) {
                    o = FixedUtil.toFixedSet((Set<?>) o);
                } else if (o instanceof Collection && type.equals(Collection.class)) {
                    o = FixedUtil.toFixedCollection((Collection<?>) o);
                }
            }
            RefUnmodifiableObject unmodifiableObject = modification.enableObject();
            if (unmodifiableObject != null && unmodifiableObject.enable()) {
                if (!Collection.class.isAssignableFrom(type)&&!Map.class.isAssignableFrom(type)) {
                    Enhancer enhancer = new Enhancer();
                    enhancer.setSuperclass(type);
                    enhancer.setCallback(new UnmodifiableMethodInterceptor(o, unmodifiableObject));
                    o = enhancer.create();
                }

            }
        }

        return o;
    }

    /**
     * 获取方法形参
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    private ArrayList<Object> getParam(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        return new ArrayList<>(Arrays.asList(joinPoint.getArgs()));
    }

    /**
     * 获取当前切面方法
     * @param joinPoint
     * @return
     * @throws NoSuchMethodException
     */
    private Method getMethod(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod();
    }
}
